package com.mmall.shiro;

import com.mmall.model.SysAcl;
import com.mmall.model.SysRole;
import com.mmall.model.SysUser;
import com.mmall.service.SysAclService;
import com.mmall.service.SysRoleAclService;
import com.mmall.service.SysRoleService;
import com.mmall.service.SysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.subject.WebSubject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * myShiroRealm
 */
@Component
public class MyShiroRealm extends AuthorizingRealm {
    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);

    @Resource
    private SysUserService userService;
    @Resource
    private SysRoleAclService sysRoleAclService;
    @Resource
    private SysAclService sysAclService;
    @Resource
    private SysRoleService roleService;
    @Resource
    private CacheManager cacheManager;

    /**
     * 认证回调函数,登录时调用.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        logger.info("===================认证回调函数,登录时调用===userService={}", userService);

        String username = token.getUsername();
        SysUser user = userService.findByKeyword(username);
        if (user != null) {
            // ShiroUser shiroUser = new ShiroUser(user.getId(),
            // user.getUsername(), user.getUsername());
            this.initSession(user);
            /**
             * 记录登录次数、时间
             */
//            userService.saveLoginTime(user);
            SimpleAuthenticationInfo authInfo = new SimpleAuthenticationInfo(user.getTelephone(), user.getPassword(), user.getUsername());
            return authInfo;
        } else {
            return null;
        }
    }

    /**
     * 设置用户session
     */
    private void initSession(SysUser user) {
        logger.info("sessionTimeOut:" + SessionConsts.sessionTimeOut);
        Session session = SecurityUtils.getSubject().getSession();
        session.setTimeout(SessionConsts.sessionTimeOut);
        session.setAttribute(SessionConsts.SESSION_USER_KEY, user);
    }


    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.
     * <p>
     * 权限信息.(授权):
     * 1、如果用户正常退出，缓存自动清空；
     * 2、如果用户非正常退出，缓存自动清空；
     * 3、如果我们修改了用户的权限，而用户不退出系统，修改的权限无法立即生效。
     * （需要手动编程进行实现；放在service进行调用）
     * 在权限修改后调用realm中的方法，realm已经由spring管理，所以从spring中获取realm实例，
     * 调用clearCached方法；
     * :Authorization 是授权访问控制，用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        String username = (String) principals.getPrimaryPrincipal();
        logger.info("===================授权查询回调函数===userService={}", userService);
        SysUser user = userService.findByKeyword(username);
        Integer userId = user.getId();
        /**
         * 把principals放session中 key=userId value=principals
         */
        SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(user.getId()), SecurityUtils.getSubject().getPrincipals());

        SimpleAuthorizationInfo info = this.authUser(userId);
        return info;
    }

    /**
     * 为用户授权.
     *
     * @param userId
     * @return
     */
    private SimpleAuthorizationInfo authUser(Integer userId) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        /**
         * 赋予角色
         */
        List<SysRole> sysRoles = roleService.getRoleListByUserId(userId);
        for (SysRole sysRole : sysRoles) {
            info.addRole(sysRole.getType().toString());
        }
        /**
         * 赋予权限
         */
        List<Integer> aclIds = sysRoleAclService.getAclIdListByRoleIdList(sysRoles.stream().map(SysRole::getId).collect(Collectors.toList()));
        Set<Integer> set=new HashSet<Integer>();
        set.addAll(aclIds);//给set填充
        List<Integer> list = new ArrayList<Integer>();
        list.addAll(set);
        List<SysAcl> sysAcls = sysAclService.getByIdList(list);

//        info.addStringPermission(sysAcls.stream().map(SysAcl::getCode).collect(Collectors.toList()));
        sysAcls.stream().forEach(sysAcl ->{
            info.addStringPermission(sysAcl.getCode());
        });
        ServletRequest request = ((WebSubject)SecurityUtils.getSubject()).getServletRequest();

        return info;
    }

    /**
     * 验证码校验.
     *
     * @param token
     * @return boolean
     */
//    protected boolean checkVerifyCode(ExtendCaptchaToken token) {
//        String captcha = (String) SecurityUtils.getSubject().getSession()
//                .getAttribute(Constants.KAPTCHA_SESSION_KEY);
//        if (captcha != null && !captcha.equalsIgnoreCase(token.getCaptcha())) {
//            throw new CaptchaException("验证码错误！");
//        }
//        return true;
//    }

    /**
     * 设定Password校验.
     */
    @PostConstruct
    public void initCredentialsMatcher() {
        /**
         * 自定义密码验证
         */
        setCredentialsMatcher(new CustomizedCredentialsMatcher(cacheManager));

    }

    /**
     * 自定义Authentication对象，使得Subject除了携带用户的登录名外还可以携带更多信息.
     */
    public static class ShiroUser implements Serializable {
        private static final long serialVersionUID = -1373760761780840081L;
        public Long id;
        public String username;
        public String name;

        public ShiroUser(Long id, String username, String name) {
            this.id = id;
            this.username = username;
            this.name = name;
        }

        public Long getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        /**
         * 本函数输出将作为默认的<shiro:principal/>输出.
         */
        @Override
        public String toString() {
            return username;
        }

        /**
         * 重载hashCode.
         */
        @Override
        public int hashCode() {
            return Objects.hashCode(username);
        }

        /**
         * 重载equals.
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            ShiroUser other = (ShiroUser) obj;
            if (username == null) {
                if (other.username != null) {
                    return false;
                }
            } else if (!username.equals(other.username)) {
                return false;
            }
            return true;
        }
    }

    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    public void clearAllCache() {
        this.clearAllCachedAuthenticationInfo();
        this.clearAllCachedAuthorizationInfo();
    }

}
